import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:mobile_app.core/core/domain/entities/settings/learning_settings.dart';
import 'package:mobile_app.core/core/domain/utils/either_extension.dart';
import 'package:mobile_app.core/purchase_flow_display/application/bloc.dart';
import 'package:mobile_app.core/settings/application/bloc/bloc.dart';
import 'package:mobile_app.core/user_info/application/bloc/bloc.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/change_training_lexical_item_max_count/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/get_initial_data/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/handle_learning_settings_outer_update/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/handle_premium_access_status_outer_update/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_audio_exercise_enablement/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_auto_pronunciation_enablement/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_exercise_shuffle_enablement/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_forgotten_lexical_item_training_enablement/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_translation_selection_exercise_enablement/use_case.dart';
import 'package:mobile_app.home.learning/learning_settings_page/domain/use_cases/toggle_typing_exercise_enablement/use_case.dart';

part 'bloc_state.dart';

class LearningSettingsPageBloc extends Cubit<LearningSettingsPageBlocState> {
  LearningSettingsPageBloc({
    required SettingsBloc settingsBloc,
    required UserInfoBloc userInfoBloc,
    required PurchaseFlowDisplayBloc purchaseFlowDisplayBloc,
    required GetInitialData getInitialData,
    required ChangeTrainingLexicalItemMaxCount changeTrainingLexicalItemMaxCount,
    required HandleLearningSettingsOuterUpdate handleLearningSettingsOuterUpdate,
    required HandlePremiumAccessStatusOuterUpdate handlePremiumAccessStatusOuterUpdate,
    required ToggleAudioExerciseEnablement toggleAudioExerciseEnablement,
    required ToggleAutoPronunciationEnablement toggleAutoPronunciationEnablement,
    required ToggleExerciseShuffleEnablement toggleExerciseShuffleEnablement,
    required ToggleForgottenLexicalItemTrainingEnablement
        toggleForgottenLexicalItemTrainingEnablement,
    required ToggleTranslationSelectionExerciseEnablement
        toggleTranslationSelectionExerciseEnablement,
    required ToggleTypingExerciseEnablement toggleTypingExerciseEnablement,
  })  : _settingsBloc = settingsBloc,
        _userInfoBloc = userInfoBloc,
        _purchaseFlowDisplayBloc = purchaseFlowDisplayBloc,
        _changeTrainingLexicalItemMaxCount = changeTrainingLexicalItemMaxCount,
        _handleLearningSettingsOuterUpdate = handleLearningSettingsOuterUpdate,
        _handlePremiumAccessStatusOuterUpdate = handlePremiumAccessStatusOuterUpdate,
        _toggleAudioExerciseEnablement = toggleAudioExerciseEnablement,
        _toggleAutoPronunciationEnablement = toggleAutoPronunciationEnablement,
        _toggleExerciseShuffleEnablement = toggleExerciseShuffleEnablement,
        _toggleForgottenLexicalItemTrainingEnablement =
            toggleForgottenLexicalItemTrainingEnablement,
        _toggleTranslationSelectionExerciseEnablement =
            toggleTranslationSelectionExerciseEnablement,
        _toggleTypingExerciseEnablement = toggleTypingExerciseEnablement,
        super(_getInitialState(
          settingsBlocState: settingsBloc.state!,
          userInfoBlocState: userInfoBloc.state!,
          getInitialData: getInitialData,
        )) {
    _settingsBlocSubscription = settingsBloc.stream.listen(_onSettingsBlocStateChanged);

    _userInfoBlocSubscription = userInfoBloc.streamWithPrevious.listen(_onUserInfoBlocStateChanged);
  }

  final SettingsBloc _settingsBloc;
  final UserInfoBloc _userInfoBloc;
  final PurchaseFlowDisplayBloc _purchaseFlowDisplayBloc;

  final ChangeTrainingLexicalItemMaxCount _changeTrainingLexicalItemMaxCount;
  final HandleLearningSettingsOuterUpdate _handleLearningSettingsOuterUpdate;
  final HandlePremiumAccessStatusOuterUpdate _handlePremiumAccessStatusOuterUpdate;
  final ToggleAudioExerciseEnablement _toggleAudioExerciseEnablement;
  final ToggleAutoPronunciationEnablement _toggleAutoPronunciationEnablement;
  final ToggleExerciseShuffleEnablement _toggleExerciseShuffleEnablement;
  final ToggleForgottenLexicalItemTrainingEnablement _toggleForgottenLexicalItemTrainingEnablement;
  final ToggleTranslationSelectionExerciseEnablement _toggleTranslationSelectionExerciseEnablement;
  final ToggleTypingExerciseEnablement _toggleTypingExerciseEnablement;

  late final StreamSubscription<SettingsBlocState> _settingsBlocSubscription;
  late final StreamSubscription<(UserInfoBlocState, UserInfoBlocState?)> _userInfoBlocSubscription;

  static LearningSettingsPageBlocState _getInitialState({
    required UserInfoBlocState userInfoBlocState,
    required SettingsBlocState settingsBlocState,
    required GetInitialData getInitialData,
  }) {
    final result = getInitialData(
      learningSettings: settingsBlocState.learningSettings,
      premiumAccessStatus: userInfoBlocState.premiumAccessStatus,
    );

    return LearningSettingsPageBlocState(
      learningSettings: settingsBlocState.learningSettings,
      exerciseDisablementDisallowed: result.exerciseDisablementDisallowed,
      trainingLexicalItemMaxCountLowerLimit: result.trainingLexicalItemMaxCountLowerLimit,
      trainingLexicalItemMaxCountUpperLimit: result.trainingLexicalItemMaxCountUpperLimit,
    );
  }

  void _onSettingsBlocStateChanged(SettingsBlocState settingsBlocState) {
    if (state.learningSettings == settingsBlocState.learningSettings) {
      return;
    }

    final result = _handleLearningSettingsOuterUpdate(
      updatedLearningSettings: settingsBlocState.learningSettings,
    );

    emit(state.copyWith(
      exerciseDisablementDisallowed: () => result.exerciseDisablementDisallowed,
    ));
  }

  void _onUserInfoBlocStateChanged((UserInfoBlocState, UserInfoBlocState?) valueWithPrevious) {
    final (userInfoBlocState, userInfoBlocPreviousState) = valueWithPrevious;

    if (userInfoBlocState.premiumAccessStatus == userInfoBlocPreviousState!.premiumAccessStatus) {
      return;
    }

    final result = _handlePremiumAccessStatusOuterUpdate(
      learningSettings: state.learningSettings,
      premiumAccessStatus: userInfoBlocState.premiumAccessStatus,
    );

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
      trainingLexicalItemMaxCountUpperLimit: () => result.trainingLexicalItemMaxCountUpperLimit,
    ));
  }

  void requestAutoPronunciationEnablementToggling() {
    final result = _toggleAutoPronunciationEnablement(
      learningSettings: state.learningSettings,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestTrainingLexicalItemMaxCountChangeStart() {
    final trainingLexicalItemMaxCountChangePermitted =
        state.learningSettings.trainingSettings.trainingLexicalItemMaxCountChangePermitted;

    if (!trainingLexicalItemMaxCountChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    emit(state.copyWith(
      trainingLexicalItemMaxCountChangeDialogShown: () => true,
    ));
  }

  void requestTrainingLexicalItemMaxCountChange({
    required int trainingLexicalItemMaxCount,
  }) {
    final trainingLexicalItemMaxCountChangePermitted =
        state.learningSettings.trainingSettings.trainingLexicalItemMaxCountChangePermitted;

    if (!trainingLexicalItemMaxCountChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    final failureOrResult = _changeTrainingLexicalItemMaxCount(
      trainingLexicalItemMaxCount: trainingLexicalItemMaxCount,
      learningSettings: state.learningSettings,
      trainingLexicalItemMaxCountLowerLimit: state.trainingLexicalItemMaxCountLowerLimit,
      trainingLexicalItemMaxCountUpperLimit: state.trainingLexicalItemMaxCountUpperLimit,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestExerciseShuffleEnablementToggling() {
    final exerciseShuffleEnablementChangePermitted =
        state.learningSettings.trainingSettings.exerciseShuffleEnablementChangePermitted;

    if (!exerciseShuffleEnablementChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    final failureOrResult = _toggleExerciseShuffleEnablement(
      learningSettings: state.learningSettings,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestForgottenLexicalItemTrainingEnablementToggling() {
    final forgottenLexicalItemTrainingEnablementChangePermitted = state
        .learningSettings.trainingSettings.forgottenLexicalItemTrainingEnablementChangePermitted;

    if (!forgottenLexicalItemTrainingEnablementChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    final failureOrResult = _toggleForgottenLexicalItemTrainingEnablement(
      learningSettings: state.learningSettings,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestTranslationSelectionExerciseEnablementToggling() {
    final trainingSettings = state.learningSettings.trainingSettings;

    if (!trainingSettings.exerciseEnablementChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    if (state.exerciseDisablementDisallowed &&
        trainingSettings.translationSelectionExerciseEnabled) {
      return;
    }

    final failureOrResult = _toggleTranslationSelectionExerciseEnablement(
      learningSettings: state.learningSettings,
      exerciseDisablementDisallowed: state.exerciseDisablementDisallowed,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestAudioExerciseEnablementToggling() {
    final trainingSettings = state.learningSettings.trainingSettings;

    if (!trainingSettings.exerciseEnablementChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    if (state.exerciseDisablementDisallowed && trainingSettings.audioExerciseEnabled) {
      return;
    }

    final failureOrResult = _toggleAudioExerciseEnablement(
      learningSettings: state.learningSettings,
      exerciseDisablementDisallowed: state.exerciseDisablementDisallowed,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  void requestTypingExerciseEnablementToggling() {
    final trainingSettings = state.learningSettings.trainingSettings;

    if (!trainingSettings.exerciseEnablementChangePermitted) {
      _purchaseFlowDisplayBloc.openPurchasePage();
      return;
    }

    if (state.exerciseDisablementDisallowed && trainingSettings.typingExerciseEnabled) {
      return;
    }

    final failureOrResult = _toggleTypingExerciseEnablement(
      learningSettings: state.learningSettings,
      exerciseDisablementDisallowed: state.exerciseDisablementDisallowed,
      premiumAccessStatus: _userInfoBloc.state!.premiumAccessStatus,
    );

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      learningSettings: () => result.learningSettings,
    ));

    _settingsBloc.updateLearningSettingsData(
      learningSettingsData: result.learningSettingsData,
    );
  }

  @override
  Future<void> close() {
    _settingsBlocSubscription.cancel();
    _userInfoBlocSubscription.cancel();
    return super.close();
  }
}
